/**
 * jquery.elastislide.js v1.1.0
 * http://www.codrops.com
 *
 * Licensed under the MIT license.
 * http://www.opensource.org/licenses/mit-license.php
 * 
 * Copyright 2012, Codrops
 * http://www.codrops.com
 */

;
(function($, window, undefined) {

    'use strict';

    /*
     * debouncedresize: special jQuery event that happens once after a window resize
     *
     * latest version and complete README available on Github:
     * https://github.com/louisremi/jquery-smartresize/blob/master/jquery.debouncedresize.js
     *
     * Copyright 2011 @louis_remi
     * Licensed under the MIT license.
     */
    var $event = $.event,
            $special,
            resizeTimeout;

    $special = $event.special.debouncedresize = {
        setup: function() {
            $(this).on("resize", $special.handler);
        },
        teardown: function() {
            $(this).off("resize", $special.handler);
        },
        handler: function(event, execAsap) {
            // Save the context
            var context = this,
                    args = arguments,
                    dispatch = function() {
                // set correct event type
                event.type = "debouncedresize";
                $event.dispatch.apply(context, args);
            };

            if (resizeTimeout) {
                clearTimeout(resizeTimeout);
            }

            execAsap ?
                    dispatch() :
                    resizeTimeout = setTimeout(dispatch, $special.threshold);
        },
        threshold: 150
    };

    // ======================= imagesLoaded Plugin ===============================
    // https://github.com/desandro/imagesloaded

    // $('#my-container').imagesLoaded(myFunction)
    // execute a callback when all images have loaded.
    // needed because .load() doesn't work on cached images

    // callback function gets image collection as argument
    //  this is the container

    // original: mit license. paul irish. 2010.
    // contributors: Oren Solomianik, David DeSandro, Yiannis Chatzikonstantinou

    // blank image data-uri bypasses webkit log warning (thx doug jones)
    var BLANK = '';

    $.fn.imagesLoaded = function(callback) {
        var $this = this,
                deferred = $.isFunction($.Deferred) ? $.Deferred() : 0,
                hasNotify = $.isFunction(deferred.notify),
                $images = $this.find('img').add($this.filter('img')),
                loaded = [],
                proper = [],
                broken = [];

        // Register deferred callbacks
        if ($.isPlainObject(callback)) {
            $.each(callback, function(key, value) {
                if (key === 'callback') {
                    callback = value;
                } else if (deferred) {
                    deferred[key](value);
                }
            });
        }

        function doneLoading() {
            var $proper = $(proper),
                    $broken = $(broken);

            if (deferred) {
                if (broken.length) {
                    deferred.reject($images, $proper, $broken);
                } else {
                    deferred.resolve($images);
                }
            }

            if ($.isFunction(callback)) {
                callback.call($this, $images, $proper, $broken);
            }
        }

        function imgLoaded(img, isBroken) {
            // don't proceed if BLANK image, or image is already loaded
            if (img.src === BLANK || $.inArray(img, loaded) !== -1) {
                return;
            }

            // store element in loaded images array
            loaded.push(img);

            // keep track of broken and properly loaded images
            if (isBroken) {
                broken.push(img);
            } else {
                proper.push(img);
            }

            // cache image and its state for future calls
            $.data(img, 'imagesLoaded', {isBroken: isBroken, src: img.src});

            // trigger deferred progress method if present
            if (hasNotify) {
                deferred.notifyWith($(img), [isBroken, $images, $(proper), $(broken)]);
            }

            // call doneLoading and clean listeners if all images are loaded
            if ($images.length === loaded.length) {
                setTimeout(doneLoading);
                $images.unbind('.imagesLoaded');
            }
        }

        // if no images, trigger immediately
        if (!$images.length) {
            doneLoading();
        } else {
            $images.bind('load.imagesLoaded error.imagesLoaded', function(event) {
                // trigger imgLoaded
                imgLoaded(event.target, event.type === 'error');
            }).each(function(i, el) {
                var src = el.src;

                // find out if this image has been already checked for status
                // if it was, and src has not changed, call imgLoaded on it
                var cached = $.data(el, 'imagesLoaded');
                if (cached && cached.src === src) {
                    imgLoaded(el, cached.isBroken);
                    return;
                }

                // if complete is true and browser supports natural sizes, try
                // to check for image status manually
                if (el.complete && el.naturalWidth !== undefined) {
                    imgLoaded(el, el.naturalWidth === 0 || el.naturalHeight === 0);
                    return;
                }

                // cached images don't fire load sometimes, so we reset src, but only when
                // dealing with IE, or image is complete (loaded) and failed manual check
                // webkit hack from http://groups.google.com/group/jquery-dev/browse_thread/thread/eee6ab7b2da50e1f
                if (el.readyState || el.complete) {
                    el.src = BLANK;
                    el.src = src;
                }
            });
        }

        return deferred ? deferred.promise($this) : $this;
    };

    // global
    var $window = $(window),
            Modernizr = window.Modernizr;

    $.Elastislide = function(options, element) {

        this.$el = $(element);
        this._init(options);

    };

    $.Elastislide.defaults = {
        // orientation 'horizontal' || 'vertical'
        orientation: 'horizontal',
        // sliding speed
        speed: 500,
        // sliding easing
        easing: 'ease-in-out',
        // the minimum number of items to show. 
        // when we resize the window, this will make sure minItems are always shown 
        // (unless of course minItems is higher than the total number of elements)
        minItems: 4,
        // index of the current item (left most item of the carousel)
        start: 0,
        autoplay: false,
        interval : 1000,
        // click item callback
        onClick: function(el, position, evt) {
            return false;
        },
        onReady: function() {
            return false;
        },
        onBeforeSlide: function() {
            return false;
        },
        onAfterSlide: function() {
            return false;
        }
    };

    $.Elastislide.prototype = {
        _init: function(options) {

            // options
            this.options = $.extend(true, {}, $.Elastislide.defaults, options);

            // https://github.com/twitter/bootstrap/issues/2870
            var self = this,
                    transEndEventNames = {
                'WebkitTransition': 'webkitTransitionEnd',
                'MozTransition': 'transitionend',
                'OTransition': 'oTransitionEnd',
                'msTransition': 'MSTransitionEnd',
                'transition': 'transitionend'
            };

            this.transEndEventName = transEndEventNames[ Modernizr.prefixed('transition') ];

            // suport for css transforms and css transitions
            this.support = Modernizr.csstransitions && Modernizr.csstransforms;

            // current item's index
            this.current = this.options.start;

            // control if it's sliding
            this.isSliding = false;

            this.$items = this.$el.children('li');
            // total number of items
            this.itemsCount = this.$items.length;
            if (this.itemsCount === 0) {

                return false;

            }
            this._validate();
            // remove white space
            this.$items.detach();
            this.$el.empty();
            this.$el.append(this.$items);

            // main wrapper
            this.$el.wrap('<div class="elastislide-wrapper elastislide-loading elastislide-' + this.options.orientation + '"></div>');

            // check if we applied a transition to the <ul>
            this.hasTransition = false;

            // add transition for the <ul>
            this.hasTransitionTimeout = setTimeout(function() {

                self._addTransition();

            }, 100);

            // preload the images

            this.$el.imagesLoaded(function() {

                self.$el.show();

                self._layout();
                self._configure();

                if (self.hasTransition) {

                    // slide to current's position
                    self._removeTransition();
                    self._slideToItem(self.current);

                    self.$el.on(self.transEndEventName, function() {

                        self.$el.off(self.transEndEventName);
                        self._setWrapperSize();
                        // add transition for the <ul>
                        self._addTransition();
                        self._initEvents();

                    });

                }
                else {

                    clearTimeout(self.hasTransitionTimeout);
                    self._setWrapperSize();
                    self._initEvents();
                    // slide to current's position
                    self._slideToItem(self.current);
                    setTimeout(function() {
                        self._addTransition();
                    }, 25);

                }

                self.options.onReady();

            });

        },
        _validate: function() {

            if (this.options.speed < 0) {

                this.options.speed = 500;

            }
            if (this.options.minItems < 1 || this.options.minItems > this.itemsCount) {

                this.options.minItems = 1;

            }
            if (this.options.start < 0 || this.options.start > this.itemsCount - 1) {

                this.options.start = 0;

            }
            if (this.options.orientation != 'horizontal' && this.options.orientation != 'vertical') {

                this.options.orientation = 'horizontal';

            }

        },
        _layout: function() {

            this.$el.wrap('<div class="elastislide-carousel"></div>');

            this.$carousel = this.$el.parent();
            this.$wrapper = this.$carousel.parent().removeClass('elastislide-loading');

            // save original image sizes
            var $img = this.$items.find('img:first');
            this.imgSize = {width: $img.outerWidth(true), height: $img.outerHeight(true)};

            this._setItemsSize();
            //this.options.orientation === 'horizontal' ? this.$el.css( 'max-height', this.imgSize.height ) : this.$el.css( 'height', this.options.minItems * this.imgSize.height );

            // add the controls
            this._addControls();

        },
        _addTransition: function() {

            if (this.support) {

                this.$el.css('transition', 'all ' + this.options.speed + 'ms ' + this.options.easing);

            }
            this.hasTransition = true;

        },
        _removeTransition: function() {

            if (this.support) {

                this.$el.css('transition', 'all 0s');

            }
            this.hasTransition = false;

        },
        _addControls: function() {

            var self = this;

            // add navigation elements
            this.$navigation = $('<nav><span class="elastislide-prev">Previous</span><span class="elastislide-next">Next</span></nav>')
                    .appendTo(this.$wrapper);


            this.$navPrev = this.$navigation.find('span.elastislide-prev').on('mousedown.elastislide', function(event) {

                self._slide('prev');
                return false;

            });

            this.$navNext = this.$navigation.find('span.elastislide-next').on('mousedown.elastislide', function(event) {

                self._slide('next');
                return false;

            });

        },
        _setItemsSize: function() {

            // width for the items (%)
            var w = this.options.orientation === 'horizontal' ? (Math.floor(this.$carousel.width() / this.options.minItems) * 100) / this.$carousel.width() : 100;

            this.$items.css({
                'width': '207px'
            });

            if (this.options.orientation === 'vertical') {

                this.$wrapper.css('max-width', this.imgSize.width + parseInt(this.$wrapper.css('padding-left')) + parseInt(this.$wrapper.css('padding-right')));

            }

        },
        _setWrapperSize: function() {

            if (this.options.orientation === 'vertical') {

                this.$wrapper.css({
                    'height': this.options.minItems * this.imgSize.height + parseInt(this.$wrapper.css('padding-top')) + parseInt(this.$wrapper.css('padding-bottom'))
                });

            }

        },
        _configure: function() {

            // check how many items fit in the carousel (visible area -> this.$carousel.width() )
            this.fitCount = this.options.orientation === 'horizontal' ?
                    this.$carousel.width() < this.options.minItems * this.imgSize.width ? this.options.minItems : Math.floor(this.$carousel.width() / this.imgSize.width) :
                    this.$carousel.height() < this.options.minItems * this.imgSize.height ? this.options.minItems : Math.floor(this.$carousel.height() / this.imgSize.height);

        },
        _initEvents: function() {

            var self = this;
            if (this.options.autoplay === true) {
                var translation = 0;
                // width/height of an item ( <li> )
                var itemSpace = this.options.orientation === 'horizontal' ? this.$items.outerWidth(true) : this.$items.outerHeight(true);
                // total width/height of the <ul>
                var totalSpace = this.itemsCount * itemSpace;
                // visible width/height
                var visibleSpace = this.options.orientation === 'horizontal' ? this.$carousel.width() : this.$carousel.height();
                //slide auto
                window.setInterval(function() {
                    //test if we should go to next slide or return to first slide
                    if (totalSpace > translation + visibleSpace)
                    {
                        //go to next slide
                        self._slide('next');
                        //update translation
                        translation += visibleSpace;
                    }
                    else
                    {
                        //return to first slide
                        self._slideTo(0);
                        //set translation to 0
                        translation = 0;
                    }
                }, this.options.interval);
            }
            $window.on('debouncedresize.elastislide', function() {

                self._setItemsSize();
                self._configure();
                self._slideToItem(self.current);

            });

            this.$el.on(this.transEndEventName, function() {

                self._onEndTransition();

            });

            if (this.options.orientation === 'horizontal') {

                this.$el.on({
                    swipeleft: function() {

                        self._slide('next');

                    },
                    swiperight: function() {

                        self._slide('prev');

                    }
                });

            }
            else {

                this.$el.on({
                    swipeup: function() {

                        self._slide('next');

                    },
                    swipedown: function() {

                        self._slide('prev');

                    }
                });

            }

            // item click event
            this.$el.on('click.elastislide', 'li', function(event) {

                var $item = $(this);

                self.options.onClick($item, $item.index(), event);

            });

        },
        _destroy: function(callback) {

            this.$el.off(this.transEndEventName).off('swipeleft swiperight swipeup swipedown .elastislide');
            $window.off('.elastislide');

            this.$el.css({
                'max-height': 'none',
                'transition': 'none'
            }).unwrap(this.$carousel).unwrap(this.$wrapper);

            this.$items.css({
                'width': 'auto',
                'max-width': 'none',
                'max-height': 'none'
            });

            this.$navigation.remove();
            this.$wrapper.remove();

            if (callback) {

                callback.call();

            }

        },
        _toggleControls: function(dir, display) {

            if (display) {

                (dir === 'next') ? this.$navNext.show() : this.$navPrev.show();

            }
            else {

                (dir === 'next') ? this.$navNext.hide() : this.$navPrev.hide();

            }

        },
        _slide: function(dir, tvalue) {

            if (this.isSliding) {

                return false;

            }

            this.options.onBeforeSlide();

            this.isSliding = true;

            var self = this,
                    translation = this.translation || 0,
                    // width/height of an item ( <li> )
                    itemSpace = this.options.orientation === 'horizontal' ? this.$items.outerWidth(true) : this.$items.outerHeight(true),
                    // total width/height of the <ul>
                    totalSpace = this.itemsCount * itemSpace,
                    // visible width/height
                    visibleSpace = this.options.orientation === 'horizontal' ? this.$carousel.width() : this.$carousel.height();

            if (tvalue === undefined) {

                var amount = this.fitCount * itemSpace;

                if (amount < 0) {

                    return false;

                }

                if (dir === 'next' && totalSpace - (Math.abs(translation) + amount) < visibleSpace) {

                    amount = totalSpace - (Math.abs(translation) + visibleSpace);

                    // show / hide navigation buttons
                    this._toggleControls('next', false);
                    this._toggleControls('prev', true);

                }
                else if (dir === 'prev' && Math.abs(translation) - amount < 0) {

                    amount = Math.abs(translation);

                    // show / hide navigation buttons
                    this._toggleControls('next', true);
                    this._toggleControls('prev', false);

                }
                else {

                    // future translation value
                    var ftv = dir === 'next' ? Math.abs(translation) + Math.abs(amount) : Math.abs(translation) - Math.abs(amount);

                    // show / hide navigation buttons
                    ftv > 0 ? this._toggleControls('prev', true) : this._toggleControls('prev', false);
                    ftv < totalSpace - visibleSpace ? this._toggleControls('next', true) : this._toggleControls('next', false);

                }

                tvalue = dir === 'next' ? translation - amount : translation + amount;

            }
            else {

                var amount = Math.abs(tvalue);

                if (Math.max(totalSpace, visibleSpace) - amount < visibleSpace) {

                    tvalue = -(Math.max(totalSpace, visibleSpace) - visibleSpace);

                }

                // show / hide navigation buttons
                amount > 0 ? this._toggleControls('prev', true) : this._toggleControls('prev', false);
                Math.max(totalSpace, visibleSpace) - visibleSpace > amount ? this._toggleControls('next', true) : this._toggleControls('next', false);

            }

            this.translation = tvalue;

            if (translation === tvalue) {

                this._onEndTransition();
                return false;

            }

            if (this.support) {

                this.options.orientation === 'horizontal' ? this.$el.css('transform', 'translateX(' + tvalue + 'px)') : this.$el.css('transform', 'translateY(' + tvalue + 'px)');

            }
            else {

                $.fn.applyStyle = this.hasTransition ? $.fn.animate : $.fn.css;
                var styleCSS = this.options.orientation === 'horizontal' ? {left: tvalue} : {top: tvalue};

                this.$el.stop().applyStyle(styleCSS, $.extend(true, [], {duration: this.options.speed, complete: function() {

                        self._onEndTransition();

                    }}));

            }

            if (!this.hasTransition) {

                this._onEndTransition();

            }

        },
        _onEndTransition: function() {

            this.isSliding = false;
            this.options.onAfterSlide();

        },
        _slideTo: function(pos) {

            var pos = pos || this.current,
                    translation = Math.abs(this.translation) || 0,
                    itemSpace = this.options.orientation === 'horizontal' ? this.$items.outerWidth(true) : this.$items.outerHeight(true),
                    posR = translation + this.$carousel.width(),
                    ftv = Math.abs(pos * itemSpace);

            if (ftv + itemSpace > posR || ftv < translation) {

                this._slideToItem(pos);

            }

        },
        _slideToItem: function(pos) {

            // how much to slide?
            var amount = this.options.orientation === 'horizontal' ? pos * this.$items.outerWidth(true) : pos * this.$items.outerHeight(true);
            this._slide('', -amount);

        },
        // public method: adds new items to the carousel
        /*
         
         how to use:
         var carouselEl = $( '#carousel' ),
         carousel = carouselEl.elastislide();
         ...
         
         // append or prepend new items:
         carouselEl.prepend('<li><a href="#"><img src="images/large/2.jpg" alt="image02" /></a></li>');
         
         // call the add method:
         es.add();
         
         */
        add: function(callback) {

            var self = this,
                    oldcurrent = this.current,
                    $currentItem = this.$items.eq(this.current);

            // adds new items to the carousel
            this.$items = this.$el.children('li');
            this.itemsCount = this.$items.length;
            this.current = $currentItem.index();
            this._setItemsSize();
            this._configure();
            this._removeTransition();
            oldcurrent < this.current ? this._slideToItem(this.current) : this._slide('next', this.translation);
            setTimeout(function() {
                self._addTransition();
            }, 25);

            if (callback) {

                callback.call();

            }

        },
        // public method: sets a new element as the current. slides to that position
        setCurrent: function(idx, callback) {

            this.current = idx;

            this._slideTo();

            if (callback) {

                callback.call();

            }

        },
        // public method: slides to the next set of items
        next: function() {

            self._slide('next');

        },
        // public method: slides to the previous set of items
        previous: function() {

            self._slide('prev');

        },
        // public method: slides to the first item
        slideStart: function() {

            this._slideTo(0);

        },
        // public method: slides to the last item
        slideEnd: function() {

            this._slideTo(this.itemsCount - 1);

        },
        // public method: destroys the elastislide instance
        destroy: function(callback) {

            this._destroy(callback);

        }

    };

    var logError = function(message) {

        if (window.console) {

            window.console.error(message);

        }

    };

    $.fn.elastislide = function(options) {

        var self = $.data(this, 'elastislide');

        if (typeof options === 'string') {

            var args = Array.prototype.slice.call(arguments, 1);

            this.each(function() {

                if (!self) {

                    logError("cannot call methods on elastislide prior to initialization; " +
                            "attempted to call method '" + options + "'");
                    return;

                }

                if (!$.isFunction(self[options]) || options.charAt(0) === "_") {

                    logError("no such method '" + options + "' for elastislide self");
                    return;

                }

                self[ options ].apply(self, args);

            });

        }
        else {

            this.each(function() {

                if (self) {

                    self._init();

                }
                else {

                    self = $.data(this, 'elastislide', new $.Elastislide(options, this));

                }

            });

        }

        return self;

    };

})(jQuery, window);
